package presentation.web ;

import java.rmi.RemoteException ;
import java.util.HashMap ;
import java.util.Map ;
import java.util.Set ;
import java.util.Vector ;

import jaxcent.HtmlPara ;
import jaxcent.HtmlTable ;
import jaxcent.JaxcentHtmlElement ;
import jaxcent.JaxcentPage ;
import jaxcent.Jaxception ;
import jaxcentPlus.JaxcentSessionListener ;
import presentation.UIMember ;
import presentation.UIUser ;
import util.Bug ;
import util.Log ;
import util.Subsystem ;

import communication.Webserver ;

import domain.ForumLogic ;
import domain.user.Privilege ;

/**
 * base class for all forum pages.
 * features:
 * - access to forum logic.
 * - mapping of page names to actual page filenames.
 * - navigating to mapped pages.
 * - error handling.
 * - creating header and footer.
 * - making links.
 * - parse page parameters.
 * - setup and retrieve the user on session data.
 *
 * page parameters go in name=value pairs with '&' between them. the character '?' separates between the page
 * filename and the parameters.
 * @author idan
 */
public class MainPage extends JaxcentPage
{
	/** name of the hompage. */
	public static final String HOME_PAGE = "view directory" ;

	/** maps page names to page filenames. */
	protected static Map<String, String> pageLinks ;

	private HtmlPara footer ;
	private HtmlTable header ;
	private HtmlPara username ;

	static
	{
		String[] pages =
		{
			"edit profile",			"login",			"logout",		"register",			"search",
			"view directory",		"view thread",		"admin page",	"statistics",		"add thread",
			"error"
		} ;
		String[] links =
		{
			"EditProfilePage",		"LoginPage",		"LogoutPage",	"RegisterPage",		"SearchPage",
			"ViewDirectoryPage",	"ViewThreadPage",	"AdminPage",	"StatisticsPage",	"AddThreadPage",
			"ErrorPage"
		} ;

		MainPage.pageLinks = new HashMap<String, String>() ;
		for (int i = 0 ; i < pages.length ; i++)
		{
			MainPage.pageLinks.put(pages[i], links[i]) ;
		}
	}

	/**
	 * @param page name of the page to link to.
	 * @param name text to be displayed for the link.
	 * @return html for the link.
	 */
	public static String makeLink(final String page, final String name)
	{
		return "<a href=\"" + page + ".html\">" + name + "</a>" ;
	}

	/**
	 * @param page name of the page to link to.
	 * @param name text to be displayed for the link.
	 * @param params string of parameters for the link.
	 * @return html for the link.
	 */
	public static String makeLink(final String page, final String name, final String params)
	{
		return "<a href=\"" + page + ".html?" + params + "\">" + name + "</a>" ;
	}

	/**
	 * @param page name of the page to link to.
	 * @param name text to be displayed for the link.
	 * @param params string of parameters for the link.
	 * @return html for the link to open in new page.
	 */
	public static String makeLinkOpenInNewPage(final String page, final String name, final String params)
	{
		return "<a href=\"" + page + ".html?" + params + " target=_blank >" + name + "</a>" ;
	}

	private static String makeToolbarLink(final String page)
	{
		return MainPage.makeToolbarLink(page, page) ;
	}

	private static String makeToolbarLink(final String page, final String name)
	{
		return MainPage.makeLink(MainPage.pageLinks.get(page), name) ;
	}

	/**
	 * creates a forum page.
	 */
	public MainPage()
	{
		try
		{
			this.setUser() ;
			this.setHeader() ;
			this.setFooter() ;
		}
		catch(Exception e)
		{
			this.navigateToErrorPage(e, null);
		}
	}

	/**
	 * Replaces every <br> in the string with '\n' for editing content
	 * @param msg - the message to edit
	 * @return the fixed message
	 */
	protected String convertFromHtml(final String msg)
	{
		return msg.replace("<br>", "\n") ;
	}

	/**
	 * Replaces every '\n' in the string with <br> for presentation
	 * @param msg - the message to convert
	 * @return the fixed message
	 */
	protected String convertToHtml(final String msg)
	{
		return msg.replace("\n", "<br>") ;
	}

	/**
	 * @return the forum facade obtained from the webserver.
	 */
	protected ForumLogic getForumLogic()
	{
		return Webserver.getForumLogic() ;
	}

	/**
	 * @return parameters sent to this page via address bar.
	 * @pre
	 * @post
	 */
	protected Map<String, String> getPageParams()
	{
		Map<String, String> params = new HashMap<String, String>() ;
		try
		{	
			String url = this.getCurrentPath() ;
			int paramStart = url.indexOf('?') + 1 ;
			if (paramStart == 0)
			{
				paramStart = url.length() + 1 ;
			}
			String paramString = url ;
			int valueStart = 0 ;
			while (paramStart < url.length())
			{
				valueStart = paramString.indexOf('=', paramStart) + 1 ;
				if (valueStart == 0)
				{
					valueStart = paramString.length() + 1 ;
				}
				String param = paramString.substring(paramStart, valueStart - 1) ;

				int nextParamStart = paramString.indexOf('&', valueStart) + 1 ;
				if (nextParamStart == 0)
				{
					nextParamStart = paramString.length() + 1 ;
				}
				String value = paramString.substring(valueStart, nextParamStart - 1) ;

				params.put(param, value) ;
				paramStart = nextParamStart ;
			}
		}
		catch(Exception e)
		{
			this.navigateToErrorPage(e, null);
		}
		return params ;
	}

	/**
	 * @return current forum user from the session.
	 * @throws Jaxception if the client could not be communicated.
	 */
	protected UIUser getUser() throws Jaxception
	{
		return (UIUser)this.getAllSessionData(false).get("user") ;
	}

	/**
	 * navigate to the error page.
	 * @param error the reason to navigate to this page.
	 * @param source element whose renderer caused the error. can be null.
	 * @post the error page will display the exception name and message.
	 */
	protected void navigateToErrorPage(final Exception error, final JaxcentHtmlElement source)
	{
		String errorString = error.getMessage() == null ? "" : ": " + error.getMessage() ;
		this.navigateToErrorPage(error.getClass().getName() + errorString , source) ;
	}

	/**
	 * navigate to the error page.
	 * @param error message to display in the error page.
	 * @param source element whose renderer caused the error. can be null.
	 * @post the error page will display the error message.
	 */
	protected void navigateToErrorPage(final String error, final JaxcentHtmlElement source)
	{
		try
		{
			Log.getLogger(Subsystem.PAGE_RENDER).fine("navigating to error page") ;
			this.navigate(this.getPagePath("error") + "?error=" + error) ;
			Log.getLogger(Subsystem.PAGE_RENDER).fine("finishednavigating") ;
		}
		catch (Exception e)
		{
			Log.getLogger(Subsystem.PAGE_RENDER).severe("cannot navigate to error page.") ;
		}
	}

	/**
	 * @param pageName name of the page to navigate to.
	 * @throws Jaxception if the client could not navigate or be ordered to navigate.
	 */
	protected void navigateToPage(final String pageName) throws Jaxception
	{
		this.navigate(this.getPagePath(pageName)) ;
	}

	/**
	 * @param pageName name of the page to navigate to.
	 * @param params parameters to pass to the page.
	 * @throws Jaxception if the client could not navigate or be ordered to navigate.
	 */
	protected void navigateToPage(final String pageName, final String params) throws Jaxception
	{
		this.navigate(this.getPagePath(pageName) + "?" + params) ;
	}

	/**
	 * @see jaxcent.JaxcentPage#onLoad()
	 */
	@Override
	protected void onLoad()
	{
		this.setFormSaveEnabled(false) ;
	}

	/**
	 * create a user object for this user, if needed.
	 * @throws Jaxception if the client could not be contacted.
	 * @post this.getAllSessionData(false).get("user") != null
	 */
	@SuppressWarnings("unchecked")
	protected void setUser() throws Jaxception
	{
		try
		{
			synchronized (this.getAllSessionData(false))
			{
				if (this.getUser() == null)
				{
					UIUser me = this.getForumLogic().registerGuest() ;
					this.getAllSessionData(false).put("user", me) ;

					JaxcentSessionListener listener = new JaxcentSessionListener()
					{
						@Override
						public void actOnSessionRemoval()
						{
							try
							{
								getForumLogic().exitUser(getUser().getId()) ;
							}
							catch (Exception e)
							{
								Log.getLogger(Subsystem.PAGE_RENDER).fine("could not exit user: " + e) ;
							}
						}
					};

					this.setSessionListener(listener) ;
				}
			}
		}
		catch (RemoteException e)
		{
			throw new Bug("webserver caught remote exception", e) ;
		}
	}

	/**
	 * assure the user is allowed to view this page, by checking a specific privilege.
	 * if the user is not allowed, it will be redirected to an error page.
	 * @param privilege privilege required in order to view this page.
	 * @return whether this user is allowed to view this page or not.
	 */
	protected boolean verifyUserAllowedPage(final Privilege privilege)
	{
		try
		{
			if (this.getUser().isAllowed(privilege))
			{
				return true ;
			}
			else
			{
				this.navigateToErrorPage("you are not allowed to view this page", null) ;
				return false ;
			}
		}
		catch (Exception e)
		{
			this.navigateToErrorPage(e, null) ;
			return false ;
		}
	}

	private String getPagePath(final String page)
	{
		return MainPage.pageLinks.get(page) + ".html" ;
	}

	private String[] getToolbarButtons()
	{
		String[] result = null;
		try
		{
			UIUser me = this.getUser() ;
			Vector<String> tools = new Vector<String>() ;

			if (me == null)
			{
				throw new Bug("user not set") ;
			}
			Log.getLogger(Subsystem.PAGE_RENDER).fine("me = " + me) ;

			tools.add(MainPage.makeToolbarLink("view directory", "home")) ;

			if (me instanceof UIMember)
			{
				tools.add(MainPage.makeToolbarLink("logout")) ;
				tools.add(MainPage.makeToolbarLink("edit profile")) ;
			}
			else
			{
				tools.add(MainPage.makeToolbarLink("login")) ;
			}
			tools.add(MainPage.makeToolbarLink("search")) ;
			tools.add(MainPage.makeToolbarLink("register")) ;
			if (me.isAllowed(Privilege.GIVE_PERMISSION))
			{
				tools.add(MainPage.makeToolbarLink("admin page")) ;
			}
			if (me.isAllowed(Privilege.VIEW_STATISTICS))
			{
				tools.add(MainPage.makeToolbarLink("statistics")) ;
			}

			result = new String[tools.size()] ;
			tools.toArray(result) ;
		}
		catch (Exception e)
		{
			this.navigateToErrorPage(e, null) ;
		}
		return result ;
	}

	private void setFooter()
	{
		this.footer = new HtmlPara(this, "footer") ;
		String userStatus ;
		try
		{
			userStatus = "Currently there are " + this.getForumLogic().getActiveGuests() + " guests. <br>" ;
			Set<String> activeMembers = this.getForumLogic().getActiveMemberNames() ;
			if (!activeMembers.isEmpty())
			{
				userStatus += "active members: " + activeMembers ;
			}
		}
		catch (RemoteException e)
		{
			userStatus = "unable to get user count" ;
		}
		try
		{
			this.footer.setInnerHTML(userStatus) ;
		}
		catch (Exception e)
		{
			this.navigateToErrorPage(e, this.footer);
		}
	}

	private void setHeader()
	{
		try
		{
			this.setToolbar() ;
			this.setUsername() ;
		}
		catch(Exception e)
		{
			this.navigateToErrorPage(e, null);
		}

	}

	private void setToolbar()
	{
		String[] toolbar = this.getToolbarButtons() ;
		this.header = new HtmlTable(this, "header") ;
		try
		{
			this.header.setBorder(1) ;
			this.header.deleteAllRows() ;
			this.header.insertRow(-1, toolbar) ;
		}
		catch (Exception e)
		{
			this.navigateToErrorPage(e, this.header);
		}
	}

	private void setUsername()
	{
		try
		{
			this.username = new HtmlPara(this, "username") ;
			UIUser user = this.getUser() ;
			String name ;
			if (user instanceof UIMember)
			{
				UIMember member = (UIMember)user ;
				name = member.getUsername() ;
			}
			else
			{
				name = "guest" ;
			}
			this.username.setInnerHTML("you are logged in as " + name) ;
		}
		catch (Exception e)
		{
			this.navigateToErrorPage(e, null);
		}
	}

}
